Version SQL migration files in source control using golang-migrate or goose, run migrations as a CI step against a test database, and deploy destructive changes with multi-phase migrations.
CI: run migrations against a fresh test DB, run integration tests, then merge
Production: run migrations as a Kubernetes Job or init container before rolling deployment
Backward-compatible migrations: add columns as nullable first, backfill, then add NOT NULL constraint
Never DROP COLUMN in the same deployment that removes the code referencing it — two-phase deploy
Use advisory locks (SELECT pg_advisory_lock) to prevent concurrent migration runs